﻿//*** Guidance generated code ***//
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

using ProjectBase.Core;
using ProjectBase.Core.Web;

namespace $safeprojectname$
{
    /// <summary>
    ///     There should be placed or ProjectBase.Core common Extensions
    /// </summary>
    public static partial class MvcExtensions
    {
        #region ListView cell

        public static ICellDescription Cell<TSource, TResult>(this TSource entity, Expression<Func<TSource, TResult>> expr,
            ICellDescription cell = null)
        {
            return Cell<TSource, TResult, object>(entity, expr, cell);
        }

        public static ICellDescription Cell<TSource, TResult, TSort>(this TSource entity, Expression<Func<TSource, TResult>> expr,
            Expression<Func<TSource, TSort>> orderBy)
        {
            return Cell(entity, expr, null, orderBy);
        }

        public static ICellDescription Cell<TSource, TResult, TSort>(this TSource entity
            , Expression<Func<TSource, TResult>> expr
            , ICellDescription cell = null
            , Expression<Func<TSource, TSort>> orderBy = null)
        {
            var property = GetCompiled(expr);
            cell = GetCellDescription(expr, cell, orderBy);
            try
            {
                cell.Value = property.Invoke(entity).ToDisplay(cell.Format);
            }
            catch { }
            return cell;
        }

        private static Func<TSource, TResult> GetCompiled<TSource, TResult>(Expression<Func<TSource, TResult>> expr)
        {
            var key = typeof(TSource).FullName + "|" + expr.ToString();
            object obj;
            if (!Funcs.TryGetValue(key, out obj))
            {
                lock (Locker)
                {
                    obj = expr.Compile();
                    Funcs[key] = obj;
                }
            }
            return obj as Func<TSource, TResult>;
        }

        private static object Locker = new object();
        public static IDictionary<string, ICellDescription> Cells = new Dictionary<string, ICellDescription>();
        public static IDictionary<string, object> Funcs = new Dictionary<string, object>();

        private static ICellDescription GetCellDescription<TSource, TResult, TSort>(Expression<Func<TSource, TResult>> expr, ICellDescription cell, Expression<Func<TSource, TSort>> orderBy)
        {
            var key = expr.Parameters[0].Type.Name + "|" + expr.Body;
            ICellDescription cellOut;
            if (Cells.TryGetValue(key, out cellOut))
            {
                return cellOut.Clone();
            }

            lock (Locker)
            {
                cell = CreateCellDescription(expr, cell, orderBy);
                Cells[key] = cell;
            }
            return cell;
        }

        private static ICellDescription CreateCellDescription<TSource, TResult, TSort>(Expression<Func<TSource, TResult>> expr, ICellDescription cell, Expression<Func<TSource, TSort>> orderBy)
        {
            if (expr.Body.NodeType != ExpressionType.MemberAccess)
            {
                throw new NotSupportedException(
                    "Provided 'Expression<Func<TSource, TResult>> expr' parameter must be of a 'ExpressionType.MemberAccess'. ");
            }

            cell = cell ?? new CellDescription();

            // I. The Memeber Name represents the SortByProperty string value
            var body = expr.Body as MemberExpression;

            if (cell.HeaderName.IsNull()) // property was not set (in passed cell param)
            {
                cell.HeaderName = body.Member.Name;
            }

            // II. value type align right
            //     boolean will be rendered as the checkbox...
            if (typeof(TResult).IsValueType)
            {
                cell.Align = Align.right;
            }
            if (typeof(TResult).IsEquivalentTo(typeof(bool))
                || typeof(TResult).IsEquivalentTo(typeof(bool?)))
            {
                cell.IsBoolean = true;
                cell.Align = Align.center;
            }

            if (cell.SortProperty.IsNull() // property was not set (in passed cell param)
                || orderBy.Is()) // there is orderby expression
            {
                var orderBody = body;
                if (orderBy.Is())
                {
                    if (orderBy.Body.NodeType
                        != ExpressionType.MemberAccess)
                    {
                        throw new NotSupportedException(
                            "Provided 'Expression<Func<TSource, TSort>> orderBy = null' parameter must be of a 'ExpressionType.MemberAccess'. ");
                    }
                    orderBody = orderBy.Body as MemberExpression;
                }

                // III. let's get the SortByObject from the body
                string path = null;
                // if there is a direct 'entity.Property' the SortByObject will be set to (remain) null

                while (orderBody.Is()) // recursive SortByObject builder
                {
                    // 'entity.City.Address.Name' ==> 'City.Address'
                    var name = orderBody.Member.Name;
                    orderBody = orderBody.Expression as MemberExpression;

                    path = path.Is()
                         ? name + "." + path
                         : name;
                }
                cell.SortProperty = path;
            }
            return cell;
        }

        #endregion ListView cell

        #region Excel sheet cell

        /// <summary>
        ///     Converts the property into the Cell instance used for Excel Exporter.
        ///     The property name is Localized and used as the output header description.
        /// </summary>
        /// <typeparam name = "TSource"></typeparam>
        /// <typeparam name = "TResult"></typeparam>
        /// <param name = "entity"></param>
        /// <param name = "expr"></param>
        /// <param name = "localizedName">if provided is used as the Header name</param>
        /// <returns></returns>
        public static IExCell ExCell<TSource, TResult>(this TSource entity, Expression<Func<TSource, TResult>> expr, string localizedName = null)
        {
            if (expr.Body.NodeType
                != ExpressionType.MemberAccess)
            {
                throw new NotSupportedException(
                    "Provided 'Expression<Func<TSource, TResult>> expr' parameter must be of a 'ExpressionType.MemberAccess'. ");
            }
            // I. The Memeber Name represents the SortByProperty string value
            var body = expr.Body as MemberExpression;

            // II. If the name is null, then let's get it from the expression
            if (localizedName.IsEmpty())
            {
                localizedName = body.Member.Name.GetLocalized();
            }

            // III. let's get the SortByObject from the body
            var orderBody = body;
            string path = null;
            // if there is a direct 'entity.Property' the SortByObject will be set to (remain) null

            while (orderBody.Is()) // recursive SortByObject builder
            {
                // 'entity.City.Address.Name' ==> 'City.Address'
                var part = orderBody.Member.Name;
                orderBody = orderBody.Expression as MemberExpression;
                if (orderBody.Is())
                {
                    path = path.Is() ? part + "." + path : part;
                }
            }

            // III. Create the resulting cell without value and default type == NUMBER
            var cell = new ExCell
            {
                Name = path,
                Localized = localizedName,
                Type = FindExCellType<TResult>(),
            };

            // IV. finally set the value of this cell, use precompiled Func
            var result = GetCompiled(expr);

            try // there must be try for some cases, for instance when the Nested property object is null
            {
                // e.g. entity.Parent.Code
                var value = result(entity);
                cell.Value = value; // could be set to null for entity.Parent

                var persistent = value as IPersistentObject;
                if (persistent.Is())
                {
                    cell.Type = ExCellType.String;
                    cell.Value = persistent.IDToDisplay;
                }
            }
            catch { }

            return cell;
        }

        private static ExCellType FindExCellType<TResult>()
        {
            // should be converted to Excel date
            if (typeof(TResult).IsEquivalentTo(typeof(DateTime))
              || typeof(TResult).IsEquivalentTo(typeof(DateTime?)))
            {
                return ExCellType.DateTime;
            }
            // should it be rendered as a string?
            if (typeof(TResult).IsEquivalentTo(typeof(string))
              || typeof(TResult).IsEquivalentTo(typeof(Guid))
              || typeof(TResult).IsEquivalentTo(typeof(Guid?)))
            {
                return ExCellType.Guid;
            }

            // is it bool?
            if (typeof(TResult).IsEquivalentTo(typeof(bool))
              || typeof(TResult).IsEquivalentTo(typeof(bool?)))
            {
                return ExCellType.Boolean;
            }
            return ExCellType.Number;
        }

        #endregion Excel sheet cell
    }
}